import {_decorator, Component, director, instantiate, macro, Prefab, UITransform, Vec3} from 'cc';
import {GameEvent} from '../../game-event';
import {userInfo} from '../../user-info';
import {lobby} from "db://assets/script/lobby";
import {BattleField} from "db://assets/script/game/mainui/battle-field";
import {FishInPool} from "db://assets/script/game/fish/fish-in-pool";
import {BattleEndEvent} from "db://assets/script/common/events/battle-end-event";
import {FishGameAction} from "db://assets/script/game/constants/fish-game-action";
import {http} from "db://assets/script/common/HTTP";
import {RandomUtils} from "db://assets/script/common/utils";
import {FishPoolRankRequest, FishPoolRankResult} from '../../../proto/new-world-api/v1/fish';
import {
    FishAttackRecordReply,
    FishBattleCompleteReply,
    FishMoveCompleteReply,
    FishPreAttackReply
} from "db://assets/proto/new-world-ws/v1/internal";
import {FreshImmortal} from '../../../proto/shared/v1/wuni';
import {serverTimeHelper} from "db://assets/script/server-time-helper";
import Long from "long";
import {UserFishRemoveOthersEvent} from "db://assets/script/common/events/fish-events/user-fish-remove-others-event";
import {FishStatus} from "db://assets/proto/shared/v1/biz";
import {FishBeginMoveEvent} from "db://assets/script/common/events/fish-events/fish-begin-move-event";
import {FishBattleWalkEvent} from "db://assets/script/common/events/fish-events/fish-battle-walk-event";
import {FishGameStatus} from "db://assets/script/game/constants/fish-game-status";
import {ClearFishEvent} from "db://assets/script/common/events/clear-fish-event";
import {fishUtils} from "db://assets/script/utils/fish-utils";
import {cmm} from "db://assets/script/utils/cmm";

const {ccclass, property} = _decorator;

@ccclass('BattleInGame')
export class BattleInGame extends Component {
    @property(Prefab)
    fishInPoolPrefab!: Prefab

    @property(BattleField)
    battleField!: BattleField;

    role: FishInPool = null!;
    latestRolePosition: Vec3 | null = null

    _roleInBattle: boolean = false

    wanderer: Map<string, FishInPool> = new Map();
    wandererShowExpire: Map<string, number> = new Map();

    _screenHeight: number = 0
    _screenWidth: number = 0

    start() {
    }

    onLoad() {
        this.onEvent();
    }

    init(fishId: Long | null): void {
        this._screenHeight = this.getComponent(UITransform)!.height
        this._screenWidth = this.getComponent(UITransform)!.width
        if (fishId != null) {
            director.emit(GameEvent.changeBattleRole, fishId)
        } else {
            const fish = userInfo.loadFirstFish();
            if (fish) director.emit(GameEvent.changeBattleRole, fish.freshFish.id)
        }
        this.schedule(() => {
            if (this.role && this.role.isValid && this.wanderer.size <= 2) {
                const ff = userInfo.loadFishById(this.role.fishId)
                if (ff && (ff.status == FishStatus.moving || ff.status == FishStatus.alive)) this._randomOneInPoolRank(ff!.zoneId).then()
            }
        }, 5, macro.REPEAT_FOREVER)

        this.schedule(() => {
            const nowMs = serverTimeHelper.loadServerTime()
            this.wandererShowExpire.forEach((expiredAtMs, fishId) => {
                if (nowMs >= expiredAtMs) {
                    this.wanderer.delete(fishId)
                    this.wandererShowExpire.delete(fishId)
                    director.emit(GameEvent.clearFish, new ClearFishEvent(fishId))
                }
            })
        }, 3, macro.REPEAT_FOREVER)
    }

    onEvent() {
        director.on(GameEvent.fishInBattle, this.battle, this)
        director.on(GameEvent.fishBeginBattle, this.attackBegin, this)
        director.on(GameEvent.battleEnd, this.battleEnd, this)
        director.on(GameEvent.changeBattleRole, (fishId: Long) => {
            if (userInfo.fishInfo.currentShowFishInBattle?.eq(fishId)) return;
            lobby.instance.showChangeMask(() => this.changeRoleInfo(fishId).then())
        }, this)
        director.on(GameEvent.fishMoveComplete, this.moveComplete, this)
        director.on(GameEvent.fishBattleComplete, this.battleComplete, this)
        director.on(GameEvent.fishBeginMove, this.fishBeginMove, this)
    }

    offEvent() {
        director.off(GameEvent.fishInBattle, this.battle, this)
        director.off(GameEvent.fishBeginBattle, this.attackBegin, this)
        director.off(GameEvent.battleEnd, this.battleEnd, this)
        director.off(GameEvent.changeBattleRole, (fishId: Long) => {
            if (userInfo.fishInfo.currentShowFishInBattle?.eq(fishId)) return;
            lobby.instance.showChangeMask(() => this.changeRoleInfo(fishId).then())
        })
        director.off(GameEvent.fishMoveComplete, this.moveComplete, this)
        director.off(GameEvent.fishBattleComplete, this.battleComplete, this)
        director.off(GameEvent.fishBeginMove, this.fishBeginMove, this)
    }

    moveComplete(event: FishMoveCompleteReply) {
        if (event.fishId.neq(this.role.fishId)) return
    }

    battleComplete(event: FishBattleCompleteReply) {
        if (this.role && this.role.isValid && this.role.fishId.eq(event.fishId)) {
            lobby.instance.showToast('战斗结束', 2)
            const target = this.role.battleTarget
            if (target) {
                target.battleTarget = null
                this.role.battleTarget = null
            }
        } else {
            const fp = this.wanderer.get(event.fishId.toString())
            if (fp) {
                const nowMs = serverTimeHelper.loadServerTime()
                this.wandererShowExpire.set(event.fishId.toString(), nowMs + 2000)
                const target = fp.battleTarget
                if (target) {
                    target.battleTarget = null
                    fp.battleTarget = null
                }
            }
        }
    }

    fishBeginMove(event: FishBeginMoveEvent) {
        if (!this.role || !this.role.isValid || this.role.fishId.neq(event.fishId)) {
            director.emit(GameEvent.changeBattleRole, event.fishId)
        }
    }

    async changeRoleInfo(fishId: Long) {
        if (userInfo.fishInfo.currentShowFishInBattle?.eq(fishId)) return;
        const ff = userInfo.loadFishById(fishId)!
        if (userInfo.fishInfo.currentShowFishInBattle && this.role) {
            this.battleField.refreshCurrentMap(this.role.node.position)
            this.role.node.destroy()
            this.wanderer.forEach((v) => {
                director.emit(GameEvent.userFishRemoveOthers, new UserFishRemoveOthersEvent(v.fishId))
                v.node.destroy()
            })
            this.wandererShowExpire.clear()
            this.wanderer.clear()
        }
        const fishInPoolNode = instantiate(this.fishInPoolPrefab)
        const fishInPool = fishInPoolNode.getComponent(FishInPool)!
        // 初始位置
        const height = this._screenHeight
        const width = this._screenWidth
        this.node.addChild(fishInPoolNode)
        this.role = fishInPool
        this.role.node.position = this.latestRolePosition == null ? new Vec3(width / 2, height / 2) : this.latestRolePosition
        fishInPool.init(ff, true, false, this._screenWidth, this._screenHeight)
        if (ff.targetFishId.length > 0 && ff.status == FishStatus.dead) {
            const fidMap = cmm.array2Map(ff.targetFishId, fid => {
                return {
                    k: fid.toNumber(),
                    v: fid,
                }
            })
            fidMap.forEach((fid) => this._battleAlreadyBegin(this.role, fid))
        }
        // Tween.stopAllByTag(1000)
        director.emit(GameEvent.uiChangeZoneName, this.role.zoneId)
        if (!fishUtils.checkFishAlive(ff.status)) {
            director.emit(GameEvent.uiChangeAliveDuration, Long.fromNumber(-1))
        } else {
            director.emit(GameEvent.uiChangeAliveDuration, ff.goHomeAtMs)
        }

        userInfo.fishInfo.currentShowFishInBattle = fishId
        if (ff && ff.status == FishStatus.moving) {
            director.emit(GameEvent.fishBeginMove, new FishBeginMoveEvent(ff.id, ff.completeAtMs))
        }
    }

    /**战斗状态, 分发攻击与受攻击事件 */
    async battle(body: FishAttackRecordReply) {
        if (this.role && this.role.isValid && (this.role.fishId.eq(body.attackId) || this.role.fishId.eq(body.defenceId))) {
            await this._mineBattle(body)
        } else if (this.wanderer.has(body.attackId.toString()) || this.wanderer.has(body.defenceId.toString())) {
            await this._wanderBattle(body)
        } else {
            director.emit(GameEvent.fishBeginBeAttack, body);
        }
        director.emit(GameEvent.fishBattleWalk, new FishBattleWalkEvent(body.attackId))
        director.emit(GameEvent.fishBattleWalk, new FishBattleWalkEvent(body.defenceId))
    }


    /**战斗状态, 分发攻击与受攻击事件 */
    async attackBegin(body: FishPreAttackReply) {
        console.log('begin battle: attacker ', body.attacker.toString(), 'defender', body.defender.toString())
        let next: Promise<void>
        if (this.role && this.role.isValid && (this.role.fishId.eq(body.attacker) || this.role.fishId.eq(body.defender))) {
            next = this._mineBattleBegin(body.attacker, body.defender)
        } else if (this.wanderer.has(body.attacker.toString()) || this.wanderer.has(body.defender.toString())) {
            next = this._wanderBeginBattle(body.attacker, body.defender)
        } else {
            return Promise.resolve()
        }
        return next.then()
    }

    private async _mineBattleBegin(attackerId: Long, defenderId: Long) {
        const roleIsAttacker = this.role.fishId.eq(attackerId)
        if (roleIsAttacker) {
            let defender = this.wanderer.get(defenderId.toString())
            if (!defender) {
                lobby.instance.showToast('战斗开始!', 2)
                await this._battleBegin(this.role, defenderId)
            }
        } else {
            let attacker = this.wanderer.get(attackerId.toString())
            if (!attacker) {
                lobby.instance.showToast('战斗开始!', 2)
                await this._battleBegin(this.role, attackerId)
            }
        }
    }


    private async _mineBattle(body: FishAttackRecordReply) {
        const roleIsAttacker = this.role.fishId.eq(body.attackId)
        if (roleIsAttacker) {
            let defender = this.wanderer.get(body.defenceId.toString())
            if (!defender) {
                lobby.instance.showToast('战斗开始!', 2)
                await this._battleBegin(this.role, body.defenceId)
                defender = this.wanderer.get(body.defenceId.toString())
            }
            this.role.fireBullet(body, defender!)
            director.emit(GameEvent.fishBeginBeAttack, body)
        } else {
            let attacker = this.wanderer.get(body.attackId.toString())
            if (!attacker) {
                lobby.instance.showToast('战斗开始!', 2)
                await this._battleBegin(this.role, body.attackId)
                attacker = this.wanderer.get(body.attackId.toString())
            }
            attacker?.fireBullet(body, this.role)
            director.emit(GameEvent.fishBeginAttack, body)
        }
    }


    private async _wanderBeginBattle(attackerId: Long, defenderId: Long) {
        let attacker = this.wanderer.get(attackerId.toString())
        let defender = this.wanderer.get(defenderId.toString())
        if (!attacker || !defender) {
            if (!attacker) {
                await this._battleBegin(defender!, attackerId)
            } else {
                await this._battleBegin(attacker!, defenderId)
            }
        }
    }


    private async _wanderBattle(body: FishAttackRecordReply) {
        let attacker = this.wanderer.get(body.attackId.toString())
        let defender = this.wanderer.get(body.defenceId.toString())
        const rdTs = Math.random() * 0.5
        if (!attacker || !defender) {
            await this._wanderBeginBattle(body.attackId, body.defenceId)
            attacker = this.wanderer.get(body.attackId.toString())
            defender = this.wanderer.get(body.defenceId.toString())
        }
        if (attacker && defender) {
            this.scheduleOnce(() => {
                attacker.fireBullet(body, defender)
                director.emit(GameEvent.fishBeginAttack, body);
            }, rdTs)
        }
    }

    private async _battleBegin(wander: FishInPool, enemyId: Long) {
        const basePos = wander.node.position
        console.log(wander.fishId, ' wander begin battle: ', enemyId)
        let newEnemy: FishInPool | void = this.wanderer.get(enemyId.toString())
        if (!newEnemy) {
            console.log('enemy not found')
            const offsetX = -100 + Math.random() * 200
            newEnemy = await this.initOneWanderer(enemyId, wander.zoneId)
            if (!newEnemy) {
                console.log('init enemy failed')
                return
            }
            newEnemy.node.setPosition(new Vec3(basePos.x + offsetX, basePos.y + this._screenHeight - 260, basePos.z))
        }
        wander.gameState = FishGameStatus.PRE_BATTLE
        newEnemy.gameState = FishGameStatus.PRE_BATTLE
        wander.battleTarget = newEnemy
        newEnemy.battleTarget = wander
        const rr = -0.1 + 0.2 * Math.random() + 1
        console.log('enemy begin pos:', newEnemy.node.position)
        newEnemy.walkTo((basePos.x - newEnemy.node.position.x) * rr, basePos.y - newEnemy.node.position.y + 300, 5, FishGameAction.GO_TO_BATTLE)
    }


    private async _battleAlreadyBegin(wander: FishInPool, enemyId: Long) {
        const basePos = wander.node.position
        console.log(wander.fishId, ' wander begin battle: ', enemyId)
        let newEnemy: FishInPool | void = this.wanderer.get(enemyId.toString())
        const offsetX = -100 + Math.random() * 200
        if (!newEnemy) {
            newEnemy = await this.initOneWanderer(enemyId, wander.zoneId)
            if (!newEnemy) return
        }
        wander.gameState = FishGameStatus.IN_BATTLE
        newEnemy.gameState = FishGameStatus.IN_BATTLE
        wander.battleTarget = newEnemy
        newEnemy.battleTarget = wander
        newEnemy.node.setPosition(new Vec3(basePos.x + offsetX, basePos.y + 300, basePos.z))
    }


    battleEnd(event: BattleEndEvent) {
        if (!this._roleInBattle) {
            return
        }

        if (this.wanderer.has(event.deadFishId.toString())) {
            // this.wanderer.get(event.deadFishId).gameAction =

        } else if (this.role.fishId.eq(event.deadFishId)) {
            // this.role.gameState = FishGameStatus.DEAD
        }

        this._roleInBattle = false
        // todo 打扫战场
        if (event.win) {
            // this.enemy.node.destroy()
        } else {
            // this.role.node.destroy()
        }
    }

    createOneWanderer(fish: FreshImmortal | void, withExpire: boolean): FishInPool | undefined {
        if (!fish) return
        const nowMs = serverTimeHelper.loadServerTime()
        const fishInPoolNode = instantiate(this.fishInPoolPrefab)
        this.node.addChild(fishInPoolNode)
        const fishInPool = fishInPoolNode.getComponent(FishInPool)!
        fishInPool.init(fish!, false, true, this._screenWidth, this._screenHeight)
        this.wanderer.set(fish!.id.toString(), fishInPool)
        if (withExpire) this.wandererShowExpire.set(fish!.id.toString(), nowMs + 100_000)
        return fishInPool
    }

    async initOneWanderer(fishId: Long, zoneId: Long): Promise<FishInPool | void> {
        return userInfo.fishInfo.seeOneFish(fishId, zoneId)
            .then(fish => this.createOneWanderer(fish, false))
    }

    private async _randomOneInPoolRank(zoneId: Long) {
        const page = RandomUtils.randomInt(0, 3)
        const ranks = await this.ranks(zoneId, page);
        if (ranks && ranks!.list && ranks!.list.length <= 0) return
        const poolItem = RandomUtils.randomEle(ranks!.list.filter(it => !this.wanderer?.has(it.fish!.fish!.id.toString()) && this.role?.fishId.neq(it.fish!.fish!.id)))
        if (!poolItem) return
        const fish = poolItem.fish!.fish!
        const wander = this.createOneWanderer(fish, true)
        if (!wander) return
        const height = this._screenHeight
        const mineX = this.role.node.x
        const mineY = this.role.node.y
        const rangeX = this._screenWidth / 6
        wander.node.position = new Vec3(mineX + rangeX * 2 * (Math.random() > 0.5 ? 1 : -1), mineY + height - 100, 0)
    }

    async ranks(zoneId: Long, page: number): Promise<FishPoolRankResult | void> {
        const param = FishPoolRankRequest.create({
            zoneId: zoneId,
            page: page,
            pageSize: 11,
        })
        return http.requestAsync<FishPoolRankResult>("POST", "v1/fish/pool/rank", FishPoolRankResult, FishPoolRankRequest.encode(param).finish())
            .catch(() => console.log("获取排行榜失败"));
    }

    protected onDestroy(): void {
        this.offEvent()
        userInfo.fishInfo.currentShowFishInBattle = null
    }

    update(deltaTime: number) {
        if (this.role && this.role.isValid) {
            const mc = lobby.instance.getMapCamera()
            const worldPos = this.role.node.worldPosition
            mc.node.setWorldPosition(new Vec3(worldPos.x, worldPos.y + 250, worldPos.z))
            this.latestRolePosition = this.role.node.position
            if (!this.battleField.newMapBlock && this.battleField.inWhichMap(this.role.node.position.y) == 2) {
                this.battleField.moveBottomMap2Top()
            }
        }

    }
}
